package org.broadway.monitor;

import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadway.interfaces.Evaluator;
import org.broadway.interfaces.ResourceCollector;

/**
 * BeanMonitor is a concrete implementation of the Monitor inteface.
 * Its role is to monitor property values of a JavaBean objects.
 * As property value changes, an expression may be setup to evaluate these
 * changes and take action using the monitor's action component.
 *
 * @author vladimir
 * @see Monitor
 * @see Action
 * @see Expression
 * @see StringExpressionEvaluator
 * @see MappedResourceCollector
 *
 */
public class BeanMonitor extends AMonitor {

    private static Log log = LogFactory.getLog(BeanMonitor.class);
    private boolean scanning;
    private String expression;
    private StringExpressionEvaluator evaluator;
    private MappedResourceCollector collector;
    private boolean autoAct = true;
    private boolean actWhen = true;

    /**
	 * Constructor
	 *
	 */
    public BeanMonitor() {
        super();
        getContext().putValue(MonitorConstants.MonitorKeys.MONITOR_OBJECT.toString(), this);
        getContext().putValue(MonitorConstants.MonitorKeys.STATS_DATE_CREATED.toString(), new Date());
    }

    /**
	 * A setter for evaluation expression.  This can be in any scripting language
	 * setup by the evaluator's instance.
	 * @param exp
	 */
    public void setMonitorExpression(String exp) {
        this.expression = exp;
        this.getContext().putValue(MonitorConstants.MonitorKeys.EVALUATOR_EXPRESSION.toString(), expression);
    }
    /**
	 * Getter that returns the monitor's evaluation expression.
	 * @return
	 */

    public String getMonitorExpression() {
        return expression;
    }

    /**
	 * Setter for the monitor's evaluator object.  The evaluator
	 * uses the monitor's expression to evaluate values from the
	 * collection of resources being monitored.
	 * If the evaluated expression is true, the monitor will execute
	 * the action registered
	 */
    @Override
	public void setEvaluator(Evaluator eval) {
        evaluator = (StringExpressionEvaluator) eval;
    }
    /**
	 * Returns an instance of the monitor's evaluator.
	 */

    @Override
	public StringExpressionEvaluator getEvaluator() {
        if (evaluator == null) {
            evaluator = (StringExpressionEvaluator) super.getEvaluator();
        }
        return evaluator;
    }

    /**
	 * The resource collector is a component that keeps
	 * static registry of objects being monitored.
	 */
    @Override
	public void setResourceCollector(ResourceCollector rc) {
        collector = (MappedResourceCollector) rc;
    }

    /**
	 * Returns an instance of the resource collector.
	 */
    @Override
	public ResourceCollector getResourceCollector() {
        if (collector == null) {
            collector = (MappedResourceCollector) super.getResourceCollector();
        }
        return collector;
    }

    /**
	 * This flag indicates whether the monitor should take
	 * immediate action when the evaluator evaluates to true.
	 * If true, action is triggered immediately, otherwise, action is ignored.
	 * @param autoAct
	 */
    public void setAutoAction(boolean autoAct) {
        this.autoAct = autoAct;
    }

    /**
	 * Returns values for AutoAction flag.
	 * @return
	 */
    public boolean isAutoAction() {
        return autoAct;
    }

    /**
	 * Flag indicating which expression value will cause action to be taken.
	 * @param flag - boolean flag [true = action executed when true,
	 *   false = action executed when evaluated to false]
	 */
    public void setActWhen(boolean flag) {
        this.actWhen = flag;
    }
    /**
	 * Getter to return flag for act when clause.
	 * @return
	 */

    public boolean getActWhen() {
        return actWhen;
    }

    /**
	 * Call this method to scan resources to be monitored.
	 * Make sure to call the {@link #start()} method prior to scan.
	 */
    public void scan() throws MonitorException {
        if (scanning) {
            return;
        }
        log.debug("Scanning resources");
        if (StringUtils.isBlank(this.getMonitorExpression())) {
            throw new MonitorException("An expression is required to evaluate monitored resources.");
        }

        try {
            scanning = true;
            this.getContext().putValue(MonitorConstants.MonitorKeys.RESOURCE_COLLECTION.toString(), this.getResourceCollector().collect());
            this.getEvaluator().putParameter(MonitorConstants.MonitorKeys.CONTEXT.toString(), ((SimpleContext)getContext()).getValues());
            takeAction();
        } finally {
            scanning = false;
        }
    }

    // ******************** Private Methods ******************** //

    /**
	 * This method is called internally to setup and delegate
	 * action to be taken by the monitor.
	 *
	 * @throws MonitorException - thrown if anything happens.
	 */
    private void takeAction() throws MonitorException {
        try {
            if (this.getEvaluator() != null && this.getResourceCollector() != null) {
                // evaluate resource state
                Boolean result = (Boolean) this.getEvaluator().evaluate(this.getMonitorExpression());
                log.debug("Expression evaluated to [" + result + "]");
                getContext().putValue(MonitorConstants.MonitorKeys.EVALUATOR_RESULT.toString(), result);

                // only take action if result is true, action is defined, and autoAction is true.
                if (result == actWhen && this.getAction() != null && isAutoAction()) {
                    log.debug("Expression evaluated to [" + result + "], taking action.");
                    Object actionResult = this.getAction().execute(this.getContext());
                    getContext().putValue(MonitorConstants.MonitorKeys.ACTION_RESULT.toString(), actionResult);
                }
            }
        } catch (Throwable ex) {
            throw new MonitorException(ex);
        }
    }
}
